﻿using System;
using System.Collections.Generic;

using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Devices;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine.Core.Components
{
    public abstract class PathSamplingEngine<TPayload>
    {
        public int InitialPopulation = 10000;
        public int MaxPhotonDepth = 10;
        protected IIntersectionEngine intersectionEngine;
        protected IRayEngineScene scene;
        public IRadianceSampler SurfaceSampler;

        public long PhotonsGenerated;

        protected PathSamplingEngine(IRayEngineScene scn, IIntersectionEngine ie)
        {
            this.scene = scn;
            this.intersectionEngine = ie;
        }

        public class PathInfo
        {
            public RayData Ray;
            public int RayIndex, Depth;
            public RgbSpectrum Throughput;
            public float BsdfWeight;
            public TPayload Data;
        }

        public void StartSampling()
        {
            bool done = false;
            IRandomProvider rnd = new FastRandom();

            var photons = new List<PathInfo>();

            for (int i = 0; i < InitialPopulation; i++)
            {
                photons.Add(GeneratePhoton(scene, rnd));

            }
            PhotonsGenerated = InitialPopulation;
            var rb = new RayBuffer(RayBuffer.RayBufferSize);
            var stopIndex = 0;
            bool breaked = false;
            while (!done)
            {
                for (int index = stopIndex; index < photons.Count; index++)
                {
                    breaked = false;
                    var photonInfo = photons[index];
                    if (!rb.IsFull)
                        photonInfo.RayIndex = rb.AddRay(ref photonInfo.Ray);
                    else
                    {
                        stopIndex = index;
                        breaked = true;
                        break;
                    }
                }
                if (!breaked)
                    stopIndex = 0;
                intersectionEngine.Trace(rb);

                for (int index = 0; index < (stopIndex > 0 ? stopIndex : photons.Count); index++)
                {
                    var hit = rb.rayHits[photons[index].RayIndex];

                    if (hit.Miss() || photons[index].Depth >= MaxPhotonDepth)
                    {
                        this.OnMiss(ref hit, photons[index]);
                        photons[index] = GeneratePhoton(scene, rnd);
                        PhotonsGenerated++;
                        continue;
                    }
                    photons[index].Depth++;

                    var hitPoint = photons[index].Ray.Point(hit.Distance);
                    var hitInfo = SurfaceSampler.GetIntersection(ref photons[index].Ray, ref hit);
                    Vector wo = -photons[index].Ray.Dir;

                    float fPdf;
                    Vector wi;
                    bool specular;
                    var throughput = RgbSpectrum.Unit;

                    RgbSpectrum f = hitInfo.MMaterial.Sample_f(ref wo, out wi, ref hitInfo.Normal, ref hitInfo.ShadingNormal, ref throughput,
                                                              rnd.NextFloat(), rnd.NextFloat(), rnd.NextFloat(),
                                                              ref hitInfo.TextureData,
                                                              out fPdf, out specular) * hitInfo.Color;
                    if (f.IsBlack() || fPdf <= 0f)
                    {
                        photons[index] = GeneratePhoton(scene, rnd);
                        PhotonsGenerated++;
                        continue;
                    }

                    var continueSampling = this.OnPathHit(ref hitPoint, ref wo, ref wi, hitInfo, ref f, fPdf, photons[index]);

                    if (!continueSampling)
                    {
                        photons[index] = GeneratePhoton(scene, rnd);
                        PhotonsGenerated++;
                        continue;
                    }
                }
                rb.Reset();
                done = this.IsSamplingDone();

            }
            OnSamplingDone();
        }

        protected virtual void OnMiss(ref RayHit hit, PathInfo pathInfo)
        {
            
        }

        protected abstract PathInfo GeneratePhoton(IRayEngineScene scene, IRandomProvider rnd);

        protected abstract bool IsSamplingDone();

        protected abstract bool OnSamplingDone();

        protected abstract bool OnPathHit(ref Point hitPoint, ref Vector wo, ref Vector wi, SurfaceIntersectionData hitInfo, ref RgbSpectrum f, float fPdf, PathInfo index);



    }
}
